﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Reflection;
using System.Xml.Schema;
using System.Xml;
using System.IO;

namespace Cqrs.Infrastructure
{
    public class SmfFileParser
    {
        private XElement _smfXml;
        private SmfFileParser() { }
        public SmfFileParser(string smfFilePath)
        {
            _smfXml = XElement.Load(smfFilePath);
        }

        public SmfFileParser(XElement smfXml)
        {
            _smfXml = smfXml;
        }

        public XElement GetStateMachineXml()
        {
            try
            {
                var scxml  = _smfXml.Elements("StateMachineXml").Elements().
                    Where(e => e.Name.LocalName == "scxml").First();
                // todo 
                //ValidateScxml(scxml);
                return scxml;
            }
            catch (InvalidOperationException)
            {
                throw new Exception("State Machine scxml not found in " + _smfXml);
            }
        }

        public XElement GetStateMachineDesigner()
        {
            try
            {
                return _smfXml.Elements("StateMachineDesigner").Elements().
                    Where(e => e.Name.LocalName.ToLower() == "root").First();
            }
            catch (InvalidOperationException)
            {
                throw new Exception("State Machine designer not found in " + _smfXml);
            }
        }

        public IEnumerable<XElement> GetDomainProperties()
        {
            try
            {
                return _smfXml.Elements("DomainProperties").Elements();
            }
            catch (InvalidOperationException)
            {
                throw new Exception("State Machine designer not found in " + _smfXml);
            }
        }

        public IEnumerable<XElement> GetAdHocMethods()
        {
            try
            {
                return _smfXml.Elements("AdHocMethods").Elements();
            }
            catch (InvalidOperationException)
            {
                throw new Exception("State Machine designer not found in " + _smfXml);
            }
        }

        public string GetNamespace()
        {
            return GetDomainPropertyAttribute("Namespace");
        }

        public string GetDomainName()
        {
            string domainName = GetDomainPropertyAttribute("DomainName");
            return domainName != "" ? domainName : "[untitled]";
        }

        public string GetAssemblyLocation()
        {
            return GetDomainPropertyAttribute("AssemblyLocation");
        }

        public string GetDomainType()
        {
            return GetDomainPropertyAttribute("DomainType");
        }

        private string GetDomainPropertyAttribute(string attributeName)
        {
            string returnValue = "";
            IEnumerable<XElement> domainPropertyElements = _smfXml.Elements("DomainProperties");
            if (domainPropertyElements.Attributes(attributeName).Count() > 0)
                returnValue = domainPropertyElements.Attributes(attributeName).First().Value;
            return returnValue;
        }

        private void ValidateScxml(XElement scxml)
        {
            var assembly = Assembly.GetExecutingAssembly();
            var stream = new StreamReader(assembly.GetManifestResourceStream("Cqrs.Infrastructure.scxml.xsd"));
            XmlSchema schema = XmlSchema.Read(stream, null);

            XmlReaderSettings settings = new XmlReaderSettings();
            settings.ValidationType = ValidationType.Schema;

            XmlSchemaSet schemaSet = new XmlSchemaSet();
            schemaSet.Add(schema);
            Validate(scxml, schemaSet);
        }

        private static void Validate(XElement scxml, XmlSchemaSet schemaSet)
        {

            XmlSchema compiledSchema = null;

            foreach (XmlSchema schema in schemaSet.Schemas())
            {
                compiledSchema = schema;
            }

            XmlReaderSettings settings = new XmlReaderSettings();
            settings.Schemas.Add(compiledSchema);
            settings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);
            settings.ValidationType = ValidationType.Schema;

            byte[] byteArray = Encoding.ASCII.GetBytes(scxml.ToString());
            MemoryStream stream = new MemoryStream(byteArray);

            //Create the schema validating reader.
            XmlReader vreader = XmlReader.Create(stream);

            while (vreader.Read()) { }

            //Close the reader.
            vreader.Close();
        }

        //Display any warnings or errors.
        private static void ValidationCallBack(object sender, ValidationEventArgs args)
        {
            if (args.Severity == XmlSeverityType.Warning)
                Console.WriteLine("\tWarning: Matching schema not found.  No validation occurred." + args.Message);
            else
                Console.WriteLine("\tValidation error: " + args.Message);

        }
    }
}
